package cc.shacocloud.luanniao.core.service.impl

import cc.shacocloud.luanniao.core.config.AdminProperties
import cc.shacocloud.luanniao.core.model.constant.UserAuthRoleEnum
import cc.shacocloud.luanniao.core.model.constant.UserStatusEnum
import cc.shacocloud.luanniao.core.model.po.UserPo
import cc.shacocloud.luanniao.core.repository.UserRepository
import cc.shacocloud.luanniao.core.service.UserService
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import kotlinx.coroutines.reactor.mono
import kotlinx.coroutines.runBlocking
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

/**
 *
 * @author 思追(shaco)
 */
@Service
@Transactional
class UserServiceImpl(
    val userRepository: UserRepository,
    val adminProperties: AdminProperties,
    val passwordEncoder: PasswordEncoder,
) : UserService {

    companion object {
        private val log: Logger = LoggerFactory.getLogger(UserServiceImpl::class.java)
    }

    /**
     * 对明文密码进行编码处理
     *
     * 数据库中不存储明文密码，所以密码在存储是必须通过该方法进行加密处理
     */
    override suspend fun encodePassword(password: String): String {
        return passwordEncoder.encode(password)
    }

    /**
     * 验证从存储获取的编码密码是否与提交的原始密码匹配，然后再对其进行编码。
     * 如果密码匹配，则返回 true，如果不匹配，则返回 false。
     *
     * @param rawPassword 传入的未被加密的密码
     * @param encodedPassword 加密后的密码
     */
    override suspend fun matchesPassword(rawPassword: CharSequence, encodedPassword: String): Boolean {
        return passwordEncoder.matches(rawPassword, encodedPassword)
    }

    /**
     * 根据 id 查询 [UserPo]
     */
    override suspend fun findById(id: Long): UserPo? {
        return userRepository.findById(id).awaitSingleOrNull()
    }

    /**
     * 根据用户名查询
     */
    override suspend fun findByUsername(username: String): UserPo? {
        return userRepository.findByUsername(username).awaitSingleOrNull()
    }

    /**
     * 创建/更新系统默认管理员
     */
    @EventListener(ApplicationReadyEvent::class)
    fun initDefaultAdminAccount() = mono {
        var userPo = userRepository.findByUsername(adminProperties.username).awaitSingleOrNull()

        if (userPo != null) {
            // 不支持将已经存在的用户通过这种方式改为系统管理员
            if (userPo.role != UserAuthRoleEnum.DEFAULT_ADMIN) {
                throw IllegalArgumentException(
                    "账号 ${adminProperties.username} 已经是当前系统的 ${userPo.role} 用户，" +
                        "不支持将其设置为系统默认管理员，请使用当前系统不存在的账号进行设置！"
                )
            }
            // 修改之前的系统管理员账号
            else {
                val defaultAdminPo =
                    userRepository.findByRole(UserAuthRoleEnum.DEFAULT_ADMIN).collectList().awaitSingle()

                if (defaultAdminPo.size != 1) {
                    if (log.isWarnEnabled) {
                        log.warn(
                            "当前系统存在多个默认管理员角色账号，这是系统不允许的，这些账号将被删除，使用新设置的默认管理员账号进行覆盖！"
                        )
                    }
                    userRepository.deleteByRole(UserAuthRoleEnum.DEFAULT_ADMIN).awaitSingle()
                    userPo = null
                } else {
                    userPo = defaultAdminPo[0].apply {
                        username = adminProperties.username
                        password = encodePassword(adminProperties.password)
                        nickname = "系统管理员"
                    }
                }
            }
        }

        if (userPo == null) {
            userPo = UserPo(
                username = adminProperties.username,
                password = encodePassword(adminProperties.password),
                nickname = "系统管理员",
                status = UserStatusEnum.NORMAL,
                role = UserAuthRoleEnum.DEFAULT_ADMIN
            )
        }

        userRepository.save(userPo).awaitSingle()
    }
}